package com.litbw.dbbackend;

import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.imageio.ImageIO;

import com.litbw.dbbackend.DBManager.FieldType;
import com.litbw.event.Contest.Entry;
import com.litbw.user.Draft;
import com.litbw.user.User;

/**
 * The DBEntryAdapter class uses the DBManager functionality to speak with
 * the Entry table. This class provides convenience methods for adding,
 * updating, deleting, and retrieving Entry objects in the Entry table.
 * 
 * @author Michael Morris
 *
 */
public class DBEntryAdapter extends AbstractDBAdapter {

	/**
	 * The ONE instance of the DBEventAdapter.
	 */
	private static DBEntryAdapter _instance = null;

	protected DBEntryAdapter() {
		super("entries");
		
		// Create field setup for table
		Map<String, FieldType> fields = new HashMap<String, FieldType>();
		fields.put("id", FieldType.INTEGER);
		fields.put("owner", FieldType.TEXT);
		fields.put("contestids", FieldType.TEXT);
		fields.put("image", FieldType.BLOB);		
		fields.put("winner", FieldType.INTEGER);
		fields.put("waiversigned", FieldType.INTEGER);
		fields.put("description", FieldType.TEXT);
		
		super.setTableFields(fields);
		super.setAutoIncrement(true);
		super.setPrimary("id");
		
	}
	
	/**
	 * Factory model... Ensures there is ONLY one DBEventAdapter present at
	 * any given time.
	 */
	private static void checkAdapter() {
		
		// Make sure the DBManager has an instance.
		if (_instance == null)
			_instance = new DBEntryAdapter();
	
	}

	/**
	 * Build a string to use for contest ids.
	 * @param entry The Entry to retrieve the contest ids from.
	 * @return An SQL compatiable string.
	 */
	private static String buildIdsList(final Entry entry) {
		String rtn = "";
		
		if (entry != null && entry.getContestIds() != null
				&& !entry.getContestIds().isEmpty()) {
			
			final StringBuilder sb = new StringBuilder();
			
			final Integer[] ids = entry.getContestIds().toArray(new Integer[0]);
			
			for (int i = 0; i < ids.length; i++) {
				if (i > 0)
					sb.append(";");
				sb.append(ids[i]);
			}
			rtn = sb.toString();	
		}
		return rtn;
	}
	
	/**
	 * Add the given entry to the Entry table of the database.
	 * @param entry The Entry to save to the database.
	 * @return True if successful, false otherwise.
	 */
	public static boolean addEntry(final Entry entry) {
		
		boolean rtn = false;
		
		checkAdapter();
		
		if (_instance.checkDatabaseConnection() && entry != null) {

			// Use a prepared statement this time so that we can add the byte
			// array successfully.
			PreparedStatement prep = null;
			try {
				
				prep = _instance.db.getConnection().prepareStatement("insert into " + _instance.table_name + " values (NULL, ?, ?, ?, ?, ?, ?);");
				prep.setString(3, entry.getContestant().getUsername());
				prep.setString(5, buildIdsList(entry));
				
				byte[] bytes = bufferedImgToByteArray(entry.getDraft().getImage());
				prep.setBytes(4, bytes);
				prep.setInt(6, (entry.isWinner() ? 1 : 0));
				prep.setInt(1, (entry.isWaiverSigned() ? 1 : 0));
				prep.setString(2, entry.getDescription());
			
				prep.execute();
				
				rtn = true;
				
			} catch (SQLException sqle) {
				sqle.printStackTrace();
			}

			// Disconnect from database and change return status.
			_instance.disconnect();
			
		}
		
		return rtn;
		
	}
	
	/**
	 * Delete the recod with the given id from the Entry table.
	 * @param id The id of the record to delete.
	 * @return True if the record is NO LONGER in the database after this
	 * method call.
	 */
	public static boolean deleteEntry(final int id) {
		
		boolean rtn = false;
		
		checkAdapter();
		
		if (_instance.checkDatabaseConnection()) {
			
			// Build the sql statement.
			final String sql = "DELETE FROMT " + _instance.table_name  +
					" WHERE id=" + id;
			
			// Run the statement
			if (_instance.db.runUpdate(sql) > 0)
				rtn = true;
			
			// Disconnect from database and change return status.
			_instance.disconnect();
			
		}
		
		return rtn;
		
	}
	
	/**
	 * Update the Entry with the specified Entry.id, using the rest of the
	 * Entry object to store updated data.
	 * @param entry The Entry object to update, WITH UPDATED INFO.
	 * @return True if updated successfully, false otherwise.
	 */
	public static boolean updateEntry(final Entry entry) {
		
		boolean rtn = false;
		
		checkAdapter();
		
		if (_instance.checkDatabaseConnection() && entry != null) {
			
			// Use a prepared statement this time so that we can add the byte
			// array successfully.
			PreparedStatement prep = null;
			try {	
				prep = _instance.db.getConnection().prepareStatement("UPDATE "
						+ _instance.table_name + 
						" SET id=?, owner=?, contestids=?, image=?, winner=?, waiversigned=?, description=?" +
						" WHERE id=" + entry.getId() + ";");
				prep.setInt(1, entry.getId());
				prep.setString(2, entry.getContestant().getUsername());
				prep.setString(3, buildIdsList(entry));
				//byte[] bytes = bufferedImgToByteArray(entry.getDraft().getImage());
				//prep.setBytes(4, bytes);
				prep.setInt(5, (entry.isWinner() ? 1 : 0));
				prep.setInt(6, (entry.isWaiverSigned() ? 1 : 0));
				prep.setString(7, entry.getDescription());
			
				prep.execute();
				
				rtn = true;
			
			} catch (SQLException sqle) {
				System.err.println("could not run update: " + sqle);
			}

			// Disconnect from database and change return status.
			_instance.disconnect();
			
		}
		return rtn;
	}
	
	/**
	 * Get an Entry with the the specified Entry id.
	 * @param id The ID of the entry to retrieve.
	 * @return An Entry object or null if not found.
	 */
	public static Entry getEntry(final int id) {
		
		Entry rtn = null;
		
		checkAdapter();
		
		if (_instance.checkDatabaseConnection()) {
			
			// Build the sql statement.
			final String sql = "SELECT * FROM " + _instance.table_name +
					" WHERE id=" + id;
			
			final ResultSet rslt = _instance.db.runQuery(sql);
			
			final Entry[] all = buildEntryFromResult(rslt);
			if (all != null)
				rtn = all[0];
			
			// Disconnect from database and change return status.
			_instance.disconnect();
			
		}
		
		return rtn;

	}
	
	/**
	 * Retrieve an array of ALL Entry objects stored in the Entry table.
	 * @return An Array of Entry objects.
	 */
	public static Entry[] getAllEntry() {
		
		Entry[] rtn = null;
		
		checkAdapter();
		
		if (_instance.checkDatabaseConnection()) {
			
			// Build the sql statement.
			final String sql = "SELECT * FROM " + _instance.table_name;
			
			final ResultSet rslt = _instance.db.runQuery(sql);
			
			rtn = buildEntryFromResult(rslt);
			
			// Disconnect from database and change return status.
			_instance.disconnect();
			
		}
		
		return rtn;

	}
	
	/**
	 * Get the Entries for a specified users name, given a User object.
	 * @param usr The user object to retrieve a name from.
	 * @return An array of all Entries for the given user.
	 */
	public static Entry[] getEntriesForUser(final User usr) {
		
		if (usr != null)
			return getEntriesForUser(usr.getUsername());
		return null;
		
	}
	
	/**
	 * Get the Entries for a specified users username/email.
	 * @param user_name The users username/email.
	 * @return An array of all Entries for the given user.
	 */
	public static Entry[] getEntriesForUser(final String user_name) {
		
		Entry[] rtn = null;
		
		checkAdapter();
		
		if (_instance.checkDatabaseConnection() && user_name != null
				&& !user_name.isEmpty()) {
			
			// Build the sql statement.
			final String sql = "SELECT * FROM " + _instance.table_name +
					" WHERE owner='" + user_name + "'";
			
			final ResultSet rslt = _instance.db.runQuery(sql);
			
			rtn = buildEntryFromResult(rslt);
			
			// Disconnect from database and change return status.
			_instance.disconnect();
			
		}
		
		return rtn;
		
	}
	
	/**
	 * Get all Entries for a given contest id.
	 * @param id The contest id.
	 * @return An Array of Entry objects.
	 */
	public static Entry[] getEntriesForContest(final int id) {
		
		Entry[] rtn = null;
		
		checkAdapter();
		
		if (_instance.checkDatabaseConnection()) {
			
			// Build the sql statement.
			final String sql = "SELECT * FROM " + _instance.table_name +
					" WHERE contestids LIKE '" + id + "' OR contestids LIKE '%;"
					+ id + "' OR contestids LIKE '" + id +
					";%' OR contestids LIKE '%;" + id + ";%'";
			
			
			final ResultSet rslt = _instance.db.runQuery(sql);
			
			rtn = buildEntryFromResult(rslt);
			
			// Disconnect from database and change return status.
			_instance.disconnect();
			
		}
		
		return rtn;
		
	}

	/**
	 * Convert an array of bytes (of image data) in to a buffered image.
	 * @param ary The array of bytes.
	 * @return A buffered image if conversion was succcessful, null otherwise.
	 */
	public static BufferedImage byteArrayToBufferedImage(final byte[] ary) {
		
		BufferedImage img = null;
		
		if (ary != null) {
			
			try {
				img = (BufferedImage)ImageIO.read(
						new ByteArrayInputStream(ary));
			} catch (IOException e) {
				System.err.println("could not convert to buffered image: " +
							e.getMessage());
			}
			
		}
		
		return img;
		
	}
	
	/**
	 * Convert a buffered image in to a byte array which can be stored in a 
	 * BLOB entry within a database.
	 * @param img The buffered image to convert.
	 * @return An array of bytes (image data) or null.
	 */
	public static byte[] bufferedImgToByteArray(final BufferedImage img) {
		
		byte[] ary = null;
		
		System.out.println("image is null: " + (img == null));
		
		if (img != null) {
			
			ByteArrayOutputStream out = null;
			
			try {
				
				out = new ByteArrayOutputStream();
				
				ImageIO.write( img, "jpg", out);
				
				out.flush();
				
				ary = out.toByteArray();
			
			} catch (IOException ioe) {
				System.err.println("could not convert img to byte array: " +
							ioe.getMessage());
			} finally {
				
				if (out != null) {
					try {
						out.close();
					} catch (IOException ioe) { }
				}
				
			}
			
		}
		
		return ary;
		
	}
	
	/**
	 * Convert an image, given a file, to a byte array.
	 * @param f The File object containing a link to the image.
	 * @return An array of bytes (image data) or null.
	 */
	public static byte[] getFISByteArray(final File f) {
		
		byte[] ary = null;
		
		FileInputStream fis = null;
		
		if (f != null && f.canRead()) {
			
			// Validate that we have enough room for the bytes.
			if (f.length() > Integer.MAX_VALUE)
				throw new IllegalArgumentException("file cannot be processed; too large");
			
			try {
			
				final int max_bytes = (int)f.length();
				fis = new FileInputStream(f);
			
				byte[] tmp = new byte[max_bytes];
				
				// Read the bytes through the input stream.
				int offset = 0;
				int b_read = 0;
				while (offset < max_bytes && (b_read = fis.read()) >= 0) {
					tmp[offset] = (byte)b_read;
					offset++;
				}
				
				// Check to make sure we read all the bytes of the file.
				if (offset < max_bytes - 1) {
					throw new IOException("one or more bytes could not be read");
				}
				
				// Make the swap if all is good.
				ary = tmp;
				
			} catch (Exception e) {
				System.err.println("could not process image: " + e.getMessage());
			} finally {
				if (fis != null) {
				
					try {
						fis.close();
					} catch (IOException ioe) { }
					
				}
			}
			
		}
		
		return ary;
		
	}
	
	/**
	 * Build Entry objects from a ResultSet obtained as a result of a database
	 * query.
	 * @param set The ResultSet object returned by the database.
	 * @return An array of Entry objects.
	 */
	private static Entry[] buildEntryFromResult(final ResultSet set) {
		
		Entry[] entries = null;
		
		if (set != null) {
			
			final List<Entry> retrieved = new ArrayList<Entry>();
			
			try {
				
				while (set.next()) {
					// This try/catch block ensures that ONLY those entries
					// which cannot be built are skipped.
					try {
						
						// Gather data stored directly inside the entry table.
						
						final User owner = (User)DBUserAdapter.getUser(set.getString("owner"));
						
						if (owner == null) {
							System.err.println("matched entry to owner not in user table: " + set.getString("owner"));
							continue;
						}
						
						Draft theDraft = new Draft();
						theDraft.setUser(owner);
						Entry current = new Entry(owner, theDraft);
						
						current.setId( set.getInt("id") );
						
						// Get image data and re-buffer it. Create draft!
						final BufferedImage img = byteArrayToBufferedImage(set.getBytes("image"));
						current.getDraft().setImage(img);
						
						current.setDescription(set.getString("description"));
						current.setWaiverSigned((set.getInt("waiversigned") == 0 ? false : true));
						current.setWinner((set.getInt("winner") == 0 ? false : true));
						
						final String[] ids_str = set.getString("contestids").split(";");
						
						// Store the contest ids
						for (int i = 0; i < ids_str.length; i++)
							current.addContestId(Integer.valueOf(ids_str[i].trim()));
						
						retrieved.add(current);
						
					} catch (SQLException sqle) {

					}
					
				}
				
			} catch (SQLException sqle) {

			}
						
			if (retrieved != null && !retrieved.isEmpty())
				entries = retrieved.toArray(new Entry[0]);
			
		}
		
		return entries;
		
	}
	
}
